home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power DOS 1996 July
/
Power DOS - July 1996.iso
/
sound
/
c_labs
/
devinfo
/
fm.exe
/
FMSAMPLE.C
next >
Wrap
C/C++ Source or Header
|
1996-02-09
|
18KB
|
511 lines
/* -------------------------------------------------------------------------- */
/* */
/* (C) Copyright Creative Technology Ltd 1994-1996. All right reserved */
/* */
/* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY */
/* KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE */
/* IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR */
/* PURPOSE. */
/* */
/* You have a royalty-free right to use, modify, reproduce and */
/* distribute the Sample Files (and/or any modified version) in */
/* any way you find useful, provided that you agree that */
/* Creative has no warranty obligations or liability for any Sample Files. */
/* */
/*----------------------------------------------------------------------------*/
/*
* This Program is written using Borland C++ Ver 3.1.
* To Compile : BCC FMSAMPLE.C
* ---------------------------------------------------------------------
*
* This program is not intended to explain all the aspects of FM sound
* generation on Sound Blaster cards. The chips are too complicated for
* that. This program is just to demonstrate how to generate a tone and
* control the left and right channels. For more information on the FM
* synthesizer chip, contact Yamaha.
*
* Here's a brief description of FM: Each sound is created by two operator
* cells (called "slots" in the Yamaha documentation), a modulator and a
* carrier. When FM synthesis was invented, the output value of the
* modulator affected the frequency of the carrier. In the Yamaha chips, the
* modulator output actually affects the phase of the carrier instead of
* frequency, but this has a similar effect.
*
* Normally the modulator and carrier would probably be connected in series
* for complex sounds. For this program, I wanted a pure sine wave, so I
* connected them in parallel and turned the modulator output down all the
* way and used just the carrier.
*
* Sound Blaster 1.0 - 2.0 cards have one OPL-2 FM synthesis chip at
* addresses 2x8 and 2x9 (base + 8 and base + 9). Sound Blaster Pro version
* 1 cards (CT-1330) achieve stereo FM with two OPL-2 chips, one for each
* speaker. The left channel FM chip is at addresses 2x0 and 2x1. The right
* is at 2x2 and 2x3. Addresses 2x8 and 2x9 address both chips
* simultaneously, thus maintaining compatibility with the monaural Sound
* Blaster cards. The OPL-2 contains 18 operator cells which make up the
* nine 2-operator channels. Since the CT-1330 SB Pro has two OPL-2 chips,
* it is therefore capable of generating 9 voices in each speaker.
*
* Sound Blaster Pro version 2 (CT-1600) and Sound Blaster 16 cards have one
* OPL-3 stereo FM chip at addresses 2x0 - 2x3. The OPL-3 is separated into
* two "banks." Ports 2x0 and 2x1 control bank 0, while 2x2 and 2x3 control
* bank 1. Each bank can generate nine 2-operator voices. However, when the
* OPL-3 is reset, it switches into OPL-2 mode. It must be put into OPL-3
* mode to use the voices in bank 1 or the stereo features. For stereo
* control, each channel may be sent to the left, the right, or both
* speakers, controlled by two bits in registers C0H - C8H.
*
* The FM chips are controlled through a set of registers. The following
* table shows how operator cells and channels are related, and the register
* offsets to use.
*
* FUNCTION MODULATOR- -CARRIER-- MODULATOR- -CARRIER-- MODULATOR- -CARRIER--
* OP CELL 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
* CHANNEL 1 2 3 1 2 3 4 5 6 4 5 6 7 8 9 7 8 9
* OFFSET 00 01 02 03 04 05 08 09 0A 0B 0C 0D 10 11 12 13 14 15
*
* An example will make the use of this table clearer: suppose you want to
* set the attenuation of both of the operators of channel 4. The KSL/TOTAL LEVEL
* registers (which set the attenuation) are 40H - 55H. The modulator for
* channel 4 is op cell 7, and the carrier for channel 4 is op cell 10. The
* offsets for the modulator and carrier cells are 08H and 0BH, respectively.
* Therefore, to set the attenuation of the modulator, you would output a
* value to register 40H + 08H == 48H, and to set the carrier's attenuation,
* you would output to register 40H + 0BH == 4BH.
*
* In this program, I used just channel 1, so the registers I used were 20H,
* 40H, 60H, etc., and 23H, 43H, 63H, etc.
*
* The frequencies of each channel are controlled with a frequency number and
* a multiplier. The modulator and carrier of a channel both get the same
* frequency number, but they may be given different multipliers. Frequency
* numbers are programmed in registers A0H - A8H (low 8 bits) and B0H - B8H
* (high 2 bits). Those registers control entire channels (2 operators), not
* individual operator cells. To turn a note on, the key-on bit in the
* appropriate channel register is set. Since these registers deal with
* channels, you do not use the offsets listed in the table above. Instead,
* add (channel-1) to A0H or B0H. For example, to turn channel 1 on,
* program the frequency number in registers A0H and B0H, and set the key-on
* bit to 1 in register B0H. For channel 3, use registers A2H and B2H.
*
* Bits 2 - 4 in registers B0H - B8H are the block (octave) number for the
* channel.
*
* Multipliers for each operator cell are programmed through registers 20H -
* 35H. The table below shows what multiple number to program into the
* register to get the desired multiplier. The multiple number goes into
* bits 0 - 3 in the register. Note that it's a bit strange at the end.
*
* multiple number multiplier multiple number multiplier
* 0 1/2 8 8
* 1 1 9 9
* 2 2 10 10
* 3 3 11 10
* 4 4 12 12
* 5 5 13 12
* 6 6 14 15
* 7 7 15 15
*
* This equation shows how to calculate the required frequency number (to
* program into registers A0H - A8H and B0H - B8H) to get the desired
* frequency:
* fn=(long)f * 1048576 / b / m /50000L
* where f is the frequency in Hz,
* b is the block (octave) number between 0 and 7 inclusive, and
* m is the multiple number between 0 and 15 inclusive.
*
*/
#define STEREO // Define this for SBPro CT-1330 or later card.
#define OPL3 // Also define this for SBPro CT-1600 or later card.
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <ctype.h>
#include <dos.h>
#define KEYON 0x20 // key-on bit in regs b0 - b8
/* These are offsets from the base I/O address. */
#define FM 8 // SB (mono) ports (e.g. 228H and 229H)
#define PROFM1 0 // On CT-1330, this is left OPL-2. On CT-1600 and
// later cards, it's OPL-3 bank 0.
#define PROFM2 2 // On CT-1330, this is right OPL-2. On CT-1600 and
// later cards, it's OPL-3 bank 1.
#ifdef OPL3
#define LEFT 0x10
#define RIGHT 0x20
#endif
unsigned IOport; // Sound Blaster port address
void mydelay(unsigned long clocks)
/*
* "clocks" is clock pulses (at 1.193180 MHz) to elapse, but remember that
* normally the system timer runs in mode 3, in which it counts down by twos,
* so delay3(1193180) will only delay half a second.
*
* clocks = time * 2386360
*
* time = clocks / 2386360
*/
{
unsigned long elapsed=0;
unsigned int last,next,ncopy,diff;
/* Read the counter value. */
outp(0x43,0); /* want to read timer 0 */
last=inp(0x40); /* low byte */
last=~((inp(0x40)<< 8) + last); /* high byte */
do {
/* Read the counter value. */
outp(0x43,0); /* want to read timer 0 */
next=inp(0x40); /* low byte */
ncopy=next=~((inp(0x40)<< 8) + next); /* high byte */
next-=last; /* this is now number of elapsed clock pulses since last read */
elapsed += next; /* add to total elapsed clock pulses */
last=ncopy;
} while (elapsed<clocks);
}
int base16(char **str, unsigned *val)
/* Takes a double pointer to a string, interprets the characters as a
* base-16 number, and advances the pointer.
* Returns 0 if successful, 1 if not.
*/
{
char c;
int digit;
*val = 0;
while ( **str != ' ') {
c = toupper(**str);
if (c >= '0' && c <= '9')
digit = c - '0';
else if (c >= 'A' && c <= 'F')
digit = c - 'A' + 10;
else
return 1; // error in string
*val = *val * 16 + digit;
(*str)++;
}
return 0;
}
int base10(char **str, unsigned *val)
/* Takes a double pointer to a string, interprets the characters as a
* base-10 number, and advances the pointer.
* Returns 0 if successful, 1 if not.
*/
{
char c;
int digit;
*val = 0;
while ( **str != ' ') {
c = toupper(**str);
if (c >= '0' && c <= '9')
digit = c - '0';
else
return 1; // error in string
*val = *val * 10 + digit;
(*str)++;
}
return 0;
}
unsigned ReadBlasterEnv(unsigned *port, unsigned *irq, unsigned *dma8,
unsigned *dma16)
/* Gets the Blaster environment statement and stores the values in the
* variables whose addresses were passed to it.
* Returns:
* 0 if successful
* 1 if there was an error reading the port address.
* 2 if there was an error reading the IRQ number.
* 3 if there was an error reading the 8-bit DMA channel.
* 4 if there was an error reading the 16-bit DMA channel.
*/
{
char *env;
unsigned val;
int digit;
env = getenv("BLASTER");
while (*env) {
switch(toupper( *(env++) )) {
case 'A':
if (base16(&env, port)) // interpret port value as hex
return 1; // error
break;
case 'I':
if (base10(&env, irq)) // interpret IRQ as decimal
return 2; // error
break;
case 'D':
if (base10(&env, dma8)) // 8-bit DMA channel is decimal
return 3;
break;
case 'H':
if (base10(&env, dma16)) // 16-bit DMA channel is decimal
return 4;
break;
default:
break;
}
}
return 0;
}
void FMoutput(unsigned port, int reg, int val)
/* This outputs a value to a specified FM register at a specified FM port. */
{
outp(port, reg);
mydelay(8); /* need to wait 3.3 microsec */
outp(port+1, val);
mydelay(55); /* need to wait 23 microsec */
}
void fm(int reg, int val)
/* This function outputs a value to a specified FM register at the Sound
* Blaster (mono) port address.
*/
{
FMoutput(IOport+FM, reg, val);
}
void Profm1(int reg, int val)
/* This function outputs a value to a specified FM register at the Sound
* Blaster Pro left FM port address (or OPL-3 bank 0).
*/
{
FMoutput(IOport+PROFM1, reg, val);
}
void Profm2(int reg, int val)
/* This function outputs a value to a specified FM register at the Sound
* Blaster Pro right FM port address (or OPL-3 bank 1).
*/
{
FMoutput(IOport+PROFM2, reg, val);
}
void main(void)
{
int i,val1,val2;
int block,b,m,f,fn;
unsigned dummy;
clrscr();
ReadBlasterEnv(&IOport, &dummy, &dummy, &dummy);
#ifdef STEREO
#ifdef OPL3
printf("Program compiled for Sound Blaster Pro ver. 2 (CT-1600) and SB16 cards.\n");
#else
printf("Program compiled for Sound Blaster Pro ver. 1 (CT-1330) cards.\n");
#endif
#else
printf("Program compiled for Sound Blaster 1.0 - 2.0 cards (monaural).\n");
#endif
fm(1,0); /* must initialize this to zero */
#ifdef OPL3
Profm2(5, 1); /* set to OPL3 mode, necessary for stereo */
fm(0xC0,LEFT | RIGHT | 1); /* set both channels, parallel connection */
#else
fm(0xC0, 1); /* parallel connection */
#endif
/***************************************
* Set parameters for the carrier cell *
***************************************/
fm(0x23,0x21); /* no amplitude modulation (D7=0), no vibrato (D6=0),
* sustained envelope type (D5=1), KSR=0 (D4=0),
* frequency multiplier=1 (D4-D0=1)
*/
fm(0x43,0x0); /* no volume decrease with pitch (D7-D6=0),
* no attenuation (D5-D0=0)
*/
fm(0x63,0xff); /* fast attack (D7-D4=0xF) and decay (D3-D0=0xF) */
fm(0x83,0x05); /* high sustain level (D7-D4=0), slow release rate (D3-D0=5) */
/*****************************************
* Set parameters for the modulator cell *
*****************************************/
fm(0x20,0x20); /* sustained envelope type, frequency multiplier=0 */
fm(0x40,0x3f); /* maximum attenuation, no volume decrease with pitch */
/* Since the modulator signal is attenuated as much as possible, these
* next two values shouldn't have any effect.
*/
fm(0x60,0x44); /* slow attack and decay */
fm(0x80,0x05); /* high sustain level, slow release rate */
/*************************************************
* Generate tone from values looked up in table. *
*************************************************/
printf("440 Hz tone, values looked up in table.\n");
fm(0xa0,0x41); /* 440 Hz */
fm(0xb0,0x32); /* 440 Hz, block 0, key on */
getche();
fm(0xb0,0x12); /* key off */
/******************************************
* Generate tone from a calculated value. *
******************************************/
printf("440 Hz tone, values calculated.\n");
block=4; /* choose block=4 and m=1 */
m=1; /* m is the frequency multiple number */
f=440; /* want f=440 Hz */
b= 1 << block;
/* This is the equation to calculate frequency number from frequency. */
fn=(long)f * 1048576 / b / m /50000L;
fm(0x23,0x20 | (m & 0xF)); /* 0x20 sets sustained envelope, low nibble
* is multiple number
*/
fm(0xA0,(fn & 0xFF));
fm(0xB0,((fn >> 8) & 0x3) + (block << 2) | KEYON);
getche();
/*********************************************************
* Generate a range of octaves by changing block number. *
*********************************************************/
printf("Range of frequencies created by changing block number.\n");
for (block=0; block<=7; block++) {
printf("f=%5ld Hz (press Enter)\n",(long)440*(1 << block)/16);
fm(0xB0,((fn >> 8) & 0x3) + (block << 2) | KEYON);
getche();
}
/*****************************************************************
* Generate a range of frequencies by changing frequency number. *
*****************************************************************/
printf("Range of frequencies created by changing frequency number.\n");
block=4;
for (fn=0; fn<1024; fn++) {
fm(0xA0,(fn & 0xFF));
fm(0xB0,((fn >> 8) & 0x3) + (block << 2) | KEYON);
delay(1);
}
/********************************************************************
* Single tone again. Both channels, then if on stereo board, *
* play tone in just the left channel, then just the right channel. *
********************************************************************/
printf("440 Hz again, both channels.\n");
block=4;
fn=577; /* This number makes 440 Hz when block=4 and m=1 */
fm(0xA0,(fn & 0xFF));
fm(0xB0,((fn >> 8) & 0x3) + (block << 2) | KEYON);
#ifdef STEREO
#ifdef OPL3
/* This left and right channel stuff is the only part of this program
* that uses OPL3 mode. Everything else is available on the OPL2.
*/
getche();
printf("Left channel only\n");
fm(0xC0,LEFT | 1); /* set left channel only, parallel connection */
getche();
printf("Right channel only\n");
fm(0xC0,RIGHT | 1); /* set right channel only, parallel connection */
#else
getche();
fm(0xB0,((fn >> 8) & 0x3) + (block << 2)); // key off
printf("Left channel only\n");
Profm1(0xB0,((fn >> 8) & 0x3) + (block << 2) | KEYON);
getche();
Profm1(0xB0,((fn >> 8) & 0x3) + (block << 2)); // key off
printf("Right channel only\n");
Profm2(0xB0,((fn >> 8) & 0x3) + (block << 2) | KEYON);
#endif
#endif
/*********************************
* Attenuate the signal by 3 dB. *
*********************************/
getche();
fm(0xB0,((fn >> 8) & 0x3) + (block << 2) | KEYON);
printf("Attenuated by 3 dB.\n");
fm(0x43,4); /* attenuate by 3 dB */
getche();
fm(0xB0,((fn >> 8) & 0x3) + (block << 2));
#ifdef OPL3
/* Set OPL-3 back to OPL-2 mode, because if the next program to run was
* written for the OPL-2, then it won't set the LEFT and RIGHT bits to
* one, so no sound will be heard.
*/
Profm2(5, 0); /* set back to OPL2 mode */
#endif
}